package voting;

import java.math.BigInteger;
import java.sql.Date;
import java.util.Arrays;
import java.util.Calendar;

import com.sun.corba.se.pept.transport.Connection;

import DAL.CandidateDA;
import DAL.UserDA;
import DAL.UserMakesVoteDA;
import DAL.VotingCandidatesDA;
import DAL.VotingDA;
import encryption.Paillier;
import encryption.RSA;
import entities.Admin;
import entities.Candidate;
import entities.User;
import entities.UserMakesVote;
import entities.Voting;
import entities.VotingCandidates;
import network.Operations;
import network.Server;
import network.ServerConnection;

public class ServerVoting {
	ServerConnection connection;
	
	User currentUser;
	 
	Admin admin;
	
	boolean isAdmin=false;
	
	public ServerVoting(ServerConnection conn,User user){
		connection = conn;
		currentUser=user;
		isAdmin=false;
	}
	
	public ServerVoting(ServerConnection conn,Admin admin)
	{
		connection=conn;
		this.admin=admin;
		isAdmin=true;
	}
	/** Class Candidates Needs To be Constructed 
	Consider adding a list of Voters after 
	updating the database to show the allowed voters for this voting
	 **/
	public static boolean createVoting(int adminID,int sDay,int sMonth,int sYear,int eDay,int eMonth,int eYear,int numberOfVoters,int votingGroup,String privateKey,String publicKey,String question,String query)
	{
		Voting v = new Voting();
		v.setAdminID(adminID);
		v.setEndDate(eYear,eMonth,eDay);
		v.setNumberOfVoters(numberOfVoters);
		v.setPrivateKey(privateKey);
		v.setPublicKey(publicKey);
		v.setQuestions(question);
		v.setStartDate(sYear,sMonth,sDay);
		v.setVotingGroup(votingGroup);
		v.setWhoCanVoteQuery(query);
		return createVoting(v);
	}
	public static boolean createVoting(Voting v)
	{
		VotingDA vda = new VotingDA();
		return vda.insertVoting(v);
	}

	public static UserMakesVote[] getAllVotes(int adminID,int votingID)
	{
		UserMakesVote umv = new UserMakesVote();
		umv.setAdminID(adminID);
		umv.setVotingID(votingID);
		UserMakesVoteDA umvda = new UserMakesVoteDA();
		UserMakesVote [] result = umvda.selectUserMakesVotes(umv);
		return result;
	}
	public static String[] getAllVotesUsers(int adminID,int votingID)
	{
		UserMakesVote umv = new UserMakesVote();
		umv.setAdminID(adminID);
		umv.setVotingID(votingID);
		UserMakesVoteDA umvda = new UserMakesVoteDA();
		UserMakesVote [] result = umvda.selectUserMakesVotes(umv);
		String [] votes = new String [result.length];
		UserDA uda = new UserDA();
		User u = new User();
		for(int i = 0 ; i<result.length ; ++i)
		{
			u.setUserID(result[i].getUserIDVoter());
			User [] t = uda.selectUsers(u);
			votes[i] = t[0].getEmail()+"_"+result[i].getVotingHash();
		}
		return votes;
	}
	/** This method should be used to query the DB to view all the votings this user either created or voted in **/
    Voting[] getPendingVotings()
    {
    	return null;
    }
	public static Voting[] getAllVotings()
	{
		Voting generalVoting=new Voting();
		VotingDA vda=new VotingDA();
		Voting[]voting=vda.selectVoting(generalVoting);
		return voting;
	}
	public static String getAllVotingsMessage()
	{
		String pendingVotings="";
		String previousVotings="";
		Voting [] votings=getAllVotings();
		for(int i=0;i<votings.length;i++)
		{
			if(votings[i].getEndDate().before(Calendar.getInstance().getTime()))
				previousVotings+=votings[i].getVotingID()+"#"+votings[i].getAdminID()+"#"+votings[i].getQuestions()+"-";
			else
				pendingVotings+=votings[i].getVotingID()+"#"+votings[i].getAdminID()+"#"+votings[i].getQuestions()+"-";
		}
		if(previousVotings.length()>0&&previousVotings.charAt(previousVotings.length()-1)=='-')
			previousVotings=previousVotings.substring(0,previousVotings.length()-1);
		if(pendingVotings.length()>0&&pendingVotings.charAt(pendingVotings.length()-1)=='-')
			pendingVotings=pendingVotings.substring(0,pendingVotings.length()-1);
		return previousVotings+"&"+pendingVotings;
		
	}
	
	/** add a candidate to a voting **/
	public static boolean addCandidate(int adminID,int candidateID,int votingID)
	{
		Candidate c = new Candidate();
		c.setCandidateID(candidateID);
		
		Voting v = new Voting();
		v.setAdminID(adminID);
		v.setVotingID(votingID);
		return addCandidate(c, v);
	}
	public static boolean addCandidate(Candidate c,Voting v)
	{
		VotingCandidates vc = new VotingCandidates();
		vc.setAdminID(v.getAdminID());
		vc.setCandidateID(c.getCandidateID());
		vc.setUserIDCandidate(1);
		vc.setVotingID(v.getVotingID());
		
		VotingCandidatesDA vcda = new VotingCandidatesDA();
		return vcda.insertVotingCandidates(vc);
	}
	public void getPublicKey(String votingID)
	{
		Voting v = new Voting();
		v.setVotingID(new Integer(votingID));
		VotingDA vda = new VotingDA();
		String publicKey =  vda.selectVoting(v)[0].getPublicKey();
		byte[] encryptedMessage = new RSA().rsaEncryptPrivate(publicKey + " This#is#the#server",Server.privateKeyMod, Server.privateKetExp);
		connection.sendRSAMessage(encryptedMessage);
	}
	/** get candidates of a vote **/
	public static Candidate[] getVotingCandidates(int votingID,int adminID)
	{
		Candidate[] candidates=new Candidate[0];
		try
		{
			VotingCandidates candidate=new VotingCandidates();
			candidate.setAdminID(adminID);
			candidate.setVotingID(votingID);
			
			VotingCandidatesDA vda=new VotingCandidatesDA();
			
			VotingCandidates[] candidateIDs=vda.selectVotingCandidates(candidate);
			
			candidates=new Candidate[candidateIDs.length];
			for(int i=0;i<candidateIDs.length;i++)
			{
				Candidate toGet=new Candidate();
				toGet.setCandidateID(candidateIDs[i].getCandidateID());
				
				CandidateDA cda=new CandidateDA();
				candidates[i]=cda.selectCandidates(toGet)[0];
				if(candidates[i].getProgram() == null || candidates[i].getProgram() .equals(""))
					candidates[i].setProgram("No Program");
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		System.out.println("Num of candidates "+candidates.length);
		return candidates;
	}
	
	/** This method is used to view the vote of a user **/
	public static String getVote(int votingID,int adminID,int userIDVoter)
	{
		UserMakesVote umv = new UserMakesVote();
		umv.setAdminID(adminID);
		umv.setUserIDVoter(userIDVoter);
		umv.setVotingID(votingID);
		
		UserMakesVoteDA umvda = new UserMakesVoteDA();
		
		UserMakesVote[] votes=umvda.selectUserMakesVotes(umv);
		
		if(votes==null||votes.length==0)
			return null;
		else
			return votes[0].getVotingHash();
	}
	public static boolean createVotingWithCandidates(int adminID,int sDay,int sMonth,int sYear,int eDay,int eMonth,int eYear,int numberOfVoters,int votingGroup,String privateKey,String publicKey,String question,String query,Candidate[]candidates)
	{
		try
		{
		Voting v = new Voting();
		v.setAdminID(adminID);
		v.setEndDate(eYear,eMonth,eDay);
		v.setNumberOfVoters(numberOfVoters);
		v.setPrivateKey(privateKey);
		v.setPublicKey(publicKey);
		v.setQuestions(question);
		v.setStartDate(sYear,sMonth,sDay);
		v.setVotingGroup(votingGroup);
		v.setWhoCanVoteQuery(query);
		
		VotingDA vda = new VotingDA();
		int votingID= vda.insertVotingWithCandidates(v);
		boolean flag=votingID!=-1;
		v.setVotingID(votingID);
		
		CandidateDA cda = new CandidateDA();
		
		VotingCandidatesDA vcda = new VotingCandidatesDA();
		
		for(int i=0;i<candidates.length;i++)
		{
			int candidateID=cda.insertCandidateForVoting(candidates[i]);
			flag=flag&&candidateID!=-1;
			VotingCandidates vc=new VotingCandidates();
			vc.setAdminID(adminID);
			vc.setCandidateID(candidateID);
			vc.setVotingID(votingID);
			flag=flag&&vcda.insertVotingCandidates(vc);
			
		}
		return flag;
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		return false;
	}
	public static void checkVotingResult(Voting voting)
	{
		if(voting!=null)
		{
			if(voting.getEndDate().before(Calendar.getInstance().getTime())&& voting.getResult()==null)
			{
				String result=calculateSum(voting.getAdminID(),voting.getVotingID()).toString();
				voting.setResult(result);
				VotingDA vda=new VotingDA();
				vda.updateVoting(voting);
			}
		}
	}
	/** This method is used to get a poll**/
	public static Voting getVoting(int votingID,int adminID)
	{
		Voting v=new Voting();
		
		v.setAdminID(adminID);
		v.setVotingID(votingID);
		VotingDA vda = new VotingDA();
		Voting[] votings=vda.selectVoting(v);
		
		if(votings==null||votings.length==0)
			return null;
		else
			
			  return votings[0];
			
		
	}

	/***
	 * 
	 * @param adminID
	 * @param userID
	 * @param votingID
	 * @param votingHash
	 * @return 0 if vote stored,1 if could not store, 2 if user already voted before
	 */
	//public static int makeVote(int adminID,int userID,int votingID,String votingHash , String randomPassword , String randomPasswordStored)
	public static int makeVote(int adminID,int userID,int votingID,String votingHash , String randomPassword , String randomPasswordStored)
	{
		if(!randomPassword.equals(randomPasswordStored)){
			return 1;
		}
		
		UserMakesVote umv = new UserMakesVote();
		umv.setAdminID(adminID);
		umv.setUserIDVoter(userID);
		umv.setVotingID(votingID);
		
		System.out.println("here");
		return makeVote(umv,votingHash);
	}
	public static BigInteger calculateSum(int adminID,int votingID)
	{
		Voting voting = getVoting(votingID, adminID);
		UserMakesVote votes [] = getAllVotes(adminID, votingID);
		BigInteger [] v = new BigInteger[votes.length];
		for(int i = 0 ; i<v.length ; ++i)
			v[i] = new BigInteger(votes[i].getVotingHash());
		Paillier p = new Paillier();
		p.setPublicKey(new BigInteger(voting.getPublicKey().split("-")[0]), new BigInteger(voting.getPublicKey().split("-")[1]));
		p.setPrivateKey(new BigInteger(voting.getPrivateKey().split("-")[0]), new BigInteger(voting.getPrivateKey().split("-")[1]));
		return p.decrypt(p.mulVotes(v));
	}
	public static int makeVote(UserMakesVote umv,String votingHash)
	{
		UserMakesVoteDA umvda = new UserMakesVoteDA();
		UserMakesVote [] previous=umvda.selectUserMakesVotes(umv);
		if(previous==null||previous.length==0)
		{
			umv.setVotingHash(votingHash);
			boolean stored=umvda.insertUserMakesVote(umv);
		   if(stored) 
			   return 0; 
		   else 
			   return 1;
		}
		else
		{
			umv.setVotingHash(votingHash);
			System.out.println("-----------------------------------------------"+umv);
			umvda.updateUserMakesVote(umv);
			return 2;
		}
	}
	
	public void processRequest()
	{
		try
		{
		while(true)
		{
			
			//connection.sendMessage("loaded");
			System.out.println("sent");
			String message=connection.receiveMessage();
			if (message ==null)
				break;
			String []tokens=message.split(" ");
			if(tokens.length>0)
			{
				int operation=Integer.parseInt(tokens[0]);
				System.out.println(message);
				switch(operation)
				{
				 
				case Operations.SELECT_VOTING:
					int votingID=Integer.parseInt(tokens[1]);
					int adminID=Integer.parseInt(tokens[2]);
					Candidate[]candidates=ServerVoting.getVotingCandidates(votingID,adminID);
					Voting currentVoting=ServerVoting.getVoting(votingID,adminID);
					checkVotingResult(currentVoting);
					if(currentVoting!=null)
					{
						
						String messageVoting=Operations.SELECT_VOTING_COMPLETE+" "+currentVoting.getQuestions()+"#"+currentVoting.getPublicKey()+"#"+currentVoting.getNumberOfVoters()+"#"+currentVoting.getResult()+"#"+candidates.length+"#";
						for(int i=0;i<candidates.length;i++)
							messageVoting+=candidates[i].getCandidateID()+"#"+candidates[i].getName()+"#";
						
						connection.sendMessage(messageVoting);
					}
					else
						connection.sendMessage(Operations.SELECT_VOTING_FAILED+"");
					break;
				
				case Operations.VOTE:
					connection.sendMessage("vote started");
					String receivedMessageEncrypted = new String(connection.receiveRSAMessage() , "ISO-8859-1");
					String []recievedMessage = new RSA().rsadecrypt(receivedMessageEncrypted, Server.privateKeyMod, Server.privateKetExp).split(" ");
					System.out.println("voting started   " + Arrays.toString(recievedMessage));
					int voteMade = 0;
					if(recievedMessage.length != 4){
						voteMade = 1;
					}
					else
						voteMade =ServerVoting.makeVote(Integer.parseInt(recievedMessage[1].trim()),currentUser.getUserID(),Integer.parseInt(recievedMessage[0].trim()),recievedMessage[2] , recievedMessage[3] , connection.getRandomPassword());
					
					if(voteMade==0)
						connection.sendMessage(Operations.VOTE_COMPLETE+"");
					else
						if(voteMade==1)
						connection.sendMessage(Operations.VOTE_FAILED+"");
						else
							connection.sendMessage(Operations.VOTED_BEFORE+"");
				break;
				
				case Operations.GET_CANDIDATES:
					System.out.println("GET CANDIDATES");
				Candidate [] candidates2 = getVotingCandidates(new Integer(tokens[1]), -1);
				String result = "";
				System.out.println("Candidates "+candidates2.length);
				for(int i = 0 ; i<candidates2.length ; ++i)
					result += candidates2[i].getCandidateID()+"_"+candidates2[i].getName()+"_"+candidates2[i].getProgram()+"#";
				System.out.println(result.substring(0,result.length()-1));
				connection.sendMessage(result.substring(0,result.length()-1));
				break;
				
				case Operations.GET_VOTE:
					String vote=ServerVoting.getVote(Integer.parseInt(tokens[1]),Integer.parseInt(tokens[2]),currentUser.getUserID());
					if(vote!=null)
						connection.sendMessage(Operations.GET_VOTE_COMPLETE+" "+vote);
					else
						connection.sendMessage(Operations.GET_VOTE_FAILED+"");
					break;
					
				case Operations.CREATE_VOTING:
					
					String next[]=message.substring((Operations.CREATE_VOTING+"").length()).split("#");
					System.out.println(Arrays.toString(next));
					int sDay=Integer.parseInt(next[0].trim());
					int sMonth=Integer.parseInt(next[1].trim());
					int sYear=Integer.parseInt(next[2].trim());
					int eDay=Integer.parseInt(next[3].trim());
					int eMonth=Integer.parseInt(next[4].trim());
					int eYear=Integer.parseInt(next[5].trim());
					int noOfVoters=Integer.parseInt(next[6].trim());
					int noOfCandidates=Integer.parseInt(next[7].trim());
					int maxCandidates=Integer.parseInt(next[8].trim());
					String question=next[9];
					
					Paillier p=new Paillier();
					BigInteger [] keys=p.generateKeys(noOfVoters,noOfCandidates,maxCandidates);
					
					String publicKey=keys[0]+"-"+keys[1];
					String privateKey=keys[2]+"-"+keys[3];
					
					Candidate[] candidate=new Candidate[noOfCandidates];
					int j=10;
					for(int i=0;i<candidate.length;i++)
					{
						String [] msg= next[j].split("-");
						candidate[i]=new Candidate();
						candidate[i].setName(msg[0]);
						if(msg.length>1)
						candidate[i].setProgram(msg[1]);
						j++;
					}
					
					boolean created=createVotingWithCandidates(admin.getAdminID(),sDay,sMonth,sYear,eDay,eMonth,eYear,noOfVoters,0,privateKey,publicKey,question,"",candidate);
					System.out.println("here2");
					/*
					 * 
					 * insert candidates of voting
					 */
					if(created)
						connection.sendMessage(Operations.CREATE_VOTING_COMPLETE+"");
					else
						connection.sendMessage(Operations.CREATE_VOTING_FAILED+"");
					System.out.println("messages sent");
					break;
					
				case Operations.GET_ALL_VOTES:
					String [] votes = getAllVotesUsers(new Integer(tokens[1]), new Integer(tokens[2]));
					String r = "";
					for(int i = 0 ; i<votes.length ; ++i)
						r+= votes[i]+"#";
					if(!r.equals(""))
					connection.sendMessage(r.substring(0,r.length()-1));
					else
					connection.sendMessage("");
					break;
				case Operations.GET_PUBLIC_KEY:
					getPublicKey(tokens[1]);
					break;
				case Operations.CALCULATE_SUM:
					connection.sendMessage(calculateSum(new Integer(tokens[1]), new Integer(tokens[2])).toString());
					break;
				case Operations.GET_ALL_VOTINGS:
					connection.sendMessage(getAllVotingsMessage());
					break;
				}
			}
			}
		}
		catch(Exception e)
		{e.printStackTrace();}
	}

	public static void main (String args [])
	{
	  // System.out.println(calculate)
	}
}
